package com.xzh.spring;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class AnnotationConfigApplicationContext {
    // 存放配置类
    private Class configClass;

    // 单例池  -- 一级缓存
    private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();

    // 二级缓存
    private ConcurrentHashMap<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();

    // 三级缓存
    private ConcurrentHashMap<String, ObjectFactory> singletonFactories = new ConcurrentHashMap<>();

    // 存放BeanDefinition
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    // 存放BeanPostProcessor
    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    /**
     * 用于存储通过 @Before 注解解析出的切入点及通知
     */
    private ConcurrentHashMap<String, List<MethodWithClass>> beforeMethodMap = new ConcurrentHashMap<>();

    /**
     * 用于存储通过 @After 注解解析出的切入点及通知
     */
    private ConcurrentHashMap<String, List<MethodWithClass>> afterMethodMap = new ConcurrentHashMap<>();

    public AnnotationConfigApplicationContext() {
    }

    /**
     * 有参构造
     *
     * @param configClass
     */
    public AnnotationConfigApplicationContext(Class configClass) {
        this.configClass = configClass;
        // 扫描configClass里面规定的包
        scan(configClass);
        // 判断配置类是否开启AOP
        checkAop();

        // 在扫描完成后创建所有的单例 Bean 并放入单例池中
        createAllSingletons();

    }

    /**
     * 判断是否开启AOP，如果开启了就存入beanPostProcessorList
     */
    public void checkAop() {
        // 判断配置类是否有@EnableAspectJAutoProxy 注解
        if (configClass.isAnnotationPresent(EnableAspectJAutoProxy.class)) {
            try {
                // 将 AnnotationAwareAspectJAutoProxyCreator 实例化
                AnnotationAwareAspectJAutoProxyCreator newInstance = AnnotationAwareAspectJAutoProxyCreator.class.getDeclaredConstructor().newInstance();
                // 填充这个实例的两个属性
                newInstance.setAfterMethodMap(afterMethodMap);
                newInstance.setBeforeMethodMap(beforeMethodMap);

                // 向 beanPostProcessorList 添加实例
                beanPostProcessorList.add(newInstance);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 在扫描完成后创建所有的单例 Bean 并放入单例池中
     */
    private void createAllSingletons() {

        Set<Map.Entry<String, BeanDefinition>> entries = beanDefinitionMap.entrySet();
        for (Map.Entry<String, BeanDefinition> entry : entries) {
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            if ("singleton".equals(beanDefinition.getScope())) {
                // 调用getBean，方法内有判断逻辑和放入单例池逻辑
                Object bean = getBean(beanName);
            }
        }
    }

    /**
     * 对配置类中指定的区域进行扫描
     *
     * @param configClass 配置类的对象
     */
    private void scan(Class configClass) {
        // 判断类上是否有 @ComponentScan 注解
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            // 获取@ComponentScan注解类
            ComponentScan componentScan = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            // 得到注解的值
            String[] values = componentScan.value();
            for (String scanPath : values) {
                // 得到的值是com.xzh.testDemo.service 这种的，而我们需要的是com/xzh/testDemo/service
                // 因为我们要获取的是编译后的.class文件，编译后就是com/xzh/这种路径了
                String path = scanPath.replace(".", "/");
                // 获取应用类加载器
                ClassLoader classLoader = AnnotationConfigApplicationContext.class.getClassLoader();
                // 获取完整资源路径
                URL resource = classLoader.getResource(path);
                // 把资源路径转化成文件，此时file指向service文件夹
                File file = new File(resource.getFile());
                // 递归扫描service文件夹里面的class和文件夹，找出嵌套的所有class
                curdLoad(scanPath, classLoader, file);
            }

        }
    }

    /**
     * 递归加载各层文件夹
     *
     * @param scanPath
     * @param classLoader
     * @param file
     */
    private void curdLoad(String scanPath, ClassLoader classLoader, File file) {
        // 判断此文件是否是目录文件
        if (file.isDirectory()) {
            // 得到此目录下的所有文件
            File[] files = file.listFiles();
            for (File f : files) {
                // 得到文件名，如：UserService.class ，impl/
                String fName = f.getName();
                // 判断文件是否是类文件
                if (fName.endsWith(".class")) {
                    // 把对应的类注册到beanDefinitionMap中
                    classDefinition(scanPath, classLoader, fName);
                } else {
                    curdLoad(scanPath + "." + fName, classLoader, f);
                }

            }
        }
    }

    /**
     * 把对应的类注册到beanDefinitionMap中
     *
     * @param scanPath
     * @param classLoader
     * @param fName
     */
    private void classDefinition(String scanPath, ClassLoader classLoader, String fName) {
        // 拼接得到类的包路径  com.xzh.testDemo.service.UserService
        String className = scanPath + "." + fName.substring(0, fName.lastIndexOf("."));
        try {
            // 通过类加载器获取类对象
            Class<?> aClass = classLoader.loadClass(className);
            // 判断这个类是否有@Component 注解
            if (aClass.isAnnotationPresent(Component.class)) {
                // 获取注解类
                Component component = aClass.getAnnotation(Component.class);
                // 获取注解的参数
                String beanName = component.value();
                // 创建一个beanDefinition，从来存放这个类的信息
                BeanDefinition beanDefinition = new BeanDefinition();
                // 先把这个类信息存入，后续需要这个参数使用反射实例化对应类
                beanDefinition.setClazz(aClass);
                // 判断是否有@Scope 注解
                if (aClass.isAnnotationPresent(Scope.class)) {
                    // 有这个注解，就取对应注解值
                    Scope scope = aClass.getAnnotation(Scope.class);
                    beanDefinition.setScope(scope.value());
                } else {
                    // 没有写@Scope 注解，默认为单例
                    beanDefinition.setScope("singleton");
                }
                // 在beanDefinition赋值完成后，存入map中
                beanDefinitionMap.put(beanName, beanDefinition);
                // 判断这个class是否实现了BeanPostProcessor接口,实现了就加入List
                checkBeanPostProcessor(aClass);
                // 对添加了 @Aspect 注解的类执行额外操作
                getPointcutFromAspect(aClass);

            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private void getPointcutFromAspect(Class<?> aClass) {
        // 判断这个类是否添加@Aspect 注解
        if (aClass.isAnnotationPresent(Aspect.class)) {
            // 遍历这个类的方法
            for (Method declaredMethod : aClass.getDeclaredMethods()) {
                // 判断这个方法是否有@Before 注解
                if (declaredMethod.isAnnotationPresent(Before.class)) {
                    // 获取注解实例
                    Before annotation = declaredMethod.getAnnotation(Before.class);
                    // 获取注解参数
                    String pointcut = annotation.value();
                    // 将解析出的参数和方法本身填入 map
                    List<MethodWithClass> methodWithClassList = null;
                    if (beforeMethodMap.get(pointcut) == null) {
                        methodWithClassList = new ArrayList<>();
                    } else {
                        methodWithClassList = beforeMethodMap.get(pointcut);
                    }
                    MethodWithClass methodWithClass = new MethodWithClass(aClass, declaredMethod);
                    methodWithClassList.add(methodWithClass);
                    beforeMethodMap.put(pointcut, methodWithClassList);

                }
                // 判断这个方法是否有@After 注解
                if (declaredMethod.isAnnotationPresent(After.class)) {
                    // 获取注解实例
                    After annotation = declaredMethod.getAnnotation(After.class);
                    String pointcut = annotation.value();

                    // 将解析出的参数和方法本身填入 map
                    List<MethodWithClass> methodWithClassList = null;
                    if (afterMethodMap.get(pointcut) == null) {
                        methodWithClassList = new ArrayList<>();
                    } else {
                        methodWithClassList = afterMethodMap.get(pointcut);
                    }
                    MethodWithClass methodWithClass = new MethodWithClass(aClass, declaredMethod);
                    methodWithClassList.add(methodWithClass);
                    afterMethodMap.put(pointcut, methodWithClassList);

                }
            }
        }

    }

    /**
     * 判断这个class是否实现了BeanPostProcessor接口,实现了就加入List
     *
     * @param aClass
     */
    private void checkBeanPostProcessor(Class<?> aClass) {
        // 判断这个class是否实现了BeanPostProcessor接口
        if (BeanPostProcessor.class.isAssignableFrom(aClass)) {
            try {
                // 实现BeanPostProcessor接口 就把这个bean添加到beanPostProcessorList
                BeanPostProcessor beanPostProcessor = (BeanPostProcessor) aClass.getDeclaredConstructor().newInstance();
                beanPostProcessorList.add(beanPostProcessor);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }

        }
    }

    /**
     * 根据beanName获取对应bean的类
     *
     * @param beanName
     * @return
     */
    public Object getBean(String beanName) {
        // 判断传入的beanName是否在beanDefinitionMap中被定义过
        if (beanDefinitionMap.containsKey(beanName)) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            String scope = beanDefinition.getScope();
            // 判断bean是否为单例
            if (scope.equals("singleton")) {
                // 判断singletonObjects是否含有对应beanName,按照正常逻辑应该是有的，因为在此类的构造中就把单例bean放入单例池了
                // 但是还需要判断，避免特殊情况，有可能是：userService在创建时，需要用getBean()填充成员变量，
                // 恰好成员变量User还未被创建,那么此时单例池就不含有User
                Object bean = null;
                if (singletonObjects.containsKey(beanName)) {
                    // 拿出对应单例bean
                    bean = singletonObjects.get(beanName);
                } else {
                    // 一级缓存不存在，就在二级缓存里找
                    // 判断二级缓存是否存在对应bean
                    if (earlySingletonObjects.containsKey(beanName)) {
                        // 拿出对应单例bean
                        bean = earlySingletonObjects.get(beanName);
                    } else {
                        // 不存在，就在三级缓存里找
                        if (singletonFactories.containsKey(beanName)) {
                            bean = singletonFactories.get(beanName).getObject();
                            // 找到后 放入二级缓存中
                            earlySingletonObjects.put(beanName, bean);
                            // 从三级缓存中删除
                            singletonFactories.remove(beanName);

                        } else {
                            // 不存在，就创建一个新的
                            bean = createBean(beanName, beanDefinition);
                        }

                    }


                }

                return bean;

                // 判断是否为原型
            } else if (scope.equals("prototype")) {
                // 新创建一个bean,并且返回
                return createBean(beanName, beanDefinition);

            }

        } else {
            // 不存在意味着没有定义，则抛出异常
            throw new MySpringException("找不到对应的bean");
        }


        return null;
    }

    /**
     * 创建bean对象,对bean对象进行处理，并把单例的 Bean 对象放入单例池
     *
     * @param beanDefinition
     * @return
     */
    private Object createBean(String beanName, BeanDefinition beanDefinition) {
        // 获取class对象
        Class clazz = beanDefinition.getClazz();

        try {
            // 通过反射无参构造器创建对象
            Object newInstance = clazz.getDeclaredConstructor().newInstance();
//            // 把生成的bean对象放入二级缓存
//            earlySingletonObjects.put(beanName, newInstance);

            Object finalBean = newInstance;
            // 把 Bean 对象及其名称放入三级缓存
            singletonFactories.put(beanName, () -> getEarlyBeanReference(beanName, finalBean));

            // 通过反射给bean的属性赋值
            populateBean(clazz, newInstance);
//            // 成员变量填充后，就不需要二级缓存的对应bean了
//            earlySingletonObjects.remove(beanName);

            // 把 Bean 对象从三级缓存中删除
            singletonFactories.remove(beanName);

            // 判断这个bean 是否实现了BeanNameAware
            if (newInstance instanceof BeanNameAware) {
                // 把Object转成BeanNameAware并且调用setName方法进行赋值
                ((BeanNameAware) newInstance).setName(beanName);
            }

            // 在初始化bean之前 调用beanPostProcessorList中所有的前置初始化方法
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                newInstance = beanPostProcessor.postProcessBeforeInitialization(newInstance, beanName);
            }

            // 判断这个bean 是否实现了InitializeBean
            if (newInstance instanceof InitializeBean) {
                // 执行此接口的afterPropertiesSet方法
                ((InitializeBean) newInstance).afterPropertiesSet();
            }

            // 在初始化bean之后 调用beanPostProcessorList中所有的后置初始化方法
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                newInstance = beanPostProcessor.postProcessAfterInitialization(newInstance, beanName);
            }
            // 判断二级缓存中是否存在 Bean 对象
            if (earlySingletonObjects.containsKey(beanName)) {
                // 如果存在意味着它提前 AOP 了，需要取出
                newInstance = earlySingletonObjects.get(beanName);
            }

            // 把单例的 Bean 对象放入单例池
            if ("singleton".equals(beanDefinition.getScope())) {
                singletonObjects.put(beanName, newInstance);
            }
            // 从二级缓存中删除
            earlySingletonObjects.remove(beanName);

            return newInstance;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        return null;


    }

    /**
     * 通过反射给bean的属性赋值
     *
     * @param clazz       待填充属性的class
     * @param newInstance
     * @throws IllegalAccessException
     */
    private void populateBean(Class clazz, Object newInstance) throws IllegalAccessException {
        // 反射获取class对象的成员变量，并遍历
        for (Field field : clazz.getDeclaredFields()) {
            // 判断是否含有@AutoWired 注解
            if (field.isAnnotationPresent(AutoWired.class)) {
                // 获取成员变量的名字，然后getBean
                Object fieldBean = getBean(field.getName());
                field.setAccessible(true);
                // 把fieldBean注入新生成的实例对象
                field.set(newInstance, fieldBean);
            }
        }
    }

    /**
     * 判断传入的参数 bean 是否需要 AOP，如果需要就返回代理对象，否则返回原始对象
     *
     * @param beanName 传入 bean 的 BeanName
     * @param bean     传入待判断的 bean 对象
     * @return 根据传入参数 bean 对象来返回原始对象或代理对象
     */
    private Object getEarlyBeanReference(String beanName, Object bean) {
        // 创建一个额外的引用用于最终的返回
        Object exposedObject = bean;
        for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
            // 判断其是否为 AnnotationAwareAspectJAutoProxyCreator 的实例
            if (beanPostProcessor instanceof AnnotationAwareAspectJAutoProxyCreator) {
                // 调用其的后置处理方法
                exposedObject = beanPostProcessor.postProcessAfterInitialization(bean, beanName);


            }
        }

        return exposedObject;
    }
}
